package com.bsoft.service.impl;

import cn.hutool.core.collection.ListUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.NumberUtil;
import com.alibaba.fastjson.JSON;
import com.bsoft.Exception.CommonException;
import com.bsoft.aop.annotation.MultiDataSourceTransactional;
import com.bsoft.enums.ETicketsEnum;
import com.bsoft.mapper.business.InvoicingBoozMapper;
import com.bsoft.mapper.lis.HivPushMapper;
import com.bsoft.service.InvoicingBoozService;
import com.bsoft.utils.*;
import com.mybatisflex.core.util.CollectionUtil;
import net.sf.json.JSONArray;
import net.sf.json.JSONObject;
import oracle.sql.CLOB;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import org.springframework.util.CollectionUtils;
import org.springframework.util.DigestUtils;
import org.springframework.util.ObjectUtils;
import sun.misc.BASE64Decoder;
import sun.misc.BASE64Encoder;

import javax.annotation.Resource;
import java.io.IOException;
import java.math.BigDecimal;
import java.sql.Clob;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.util.*;
import java.util.stream.Collectors;

import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.util.List;
import java.util.Map;
import java.util.Optional;

import static com.bsoft.utils.UrlToPdfToZipToBase64Util.urlListToBase64Zip;


@Service
@EnableTransactionManagement
public class InvoicingBoozServiceImpl extends ConmonUtil implements InvoicingBoozService {

    private static Logger log = LoggerFactory.getLogger(InvoicingBoozServiceImpl.class);


    // 定义静态格式化器（线程安全，可复用）
    private static final DateTimeFormatter INPUT_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
    private static final DateTimeFormatter OUTPUT_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMdd");

    @Value("${HospitalID}")
    private String hospitalID;

    @Resource
    private InvoicingBoozMapper invoicingBoozMapper;

    @MultiDataSourceTransactional(transactionManagers = {"hisTransactionManager"})
    @Override
    public String BS_E_TicketsInvoicing(Map<String, Object> parmMap) {
        JSONObject resJson = new JSONObject();

        isNUll(parmMap, Arrays.asList("invoiceNumber", "euDirect", "jgid", "medstoeid"));
        //根据发票号码查询结算的信息
        Map<String, Object> req = invoicingBoozMapper.getSettlementInfo(parmMap);
        if (ObjectUtils.isEmpty(req)) {
            resJson.put("code", "500");
            resJson.put("msg", "未找到HIS结算信息[FPHM:" + parmMap.get("invoiceNumber") + "]!");
            resJson.put("data", new Object());
            return resJson.toString();
        }
        req.put("euDirect", parmMap.get("euDirect").toString());

        isNUll(req, Arrays.asList("idMedstCg", "sdVistpCd", "euDirect"));

        if (!"1".equals(req.get("euDirect").toString())) {
            resJson.put("code", "500");
            resJson.put("msg", "该业务是电子发票开票euDirect应该传1");
            resJson.put("data", new Object());
            return resJson.toString();
        }

        String idMedstCg = req.get("idMedstCg").toString();//结算id
        String sdVistpCd = req.get("sdVistpCd").toString();//医健卫流程 111 门诊 112 急诊 113 体检 114 住院
        if ("111".equals(sdVistpCd) || "112".equals(sdVistpCd) || "113".equals(sdVistpCd)) {
            sdVistpCd = invoicingBoozMapper.getBsSdVistpCd(idMedstCg);
        }

        //查询HIHIS是否已经有开票信息
        Map<String, Object> idMedstCgMap = invoicingBoozMapper.getBsInvoiceState(idMedstCg, parmMap.get("invoiceNumber").toString());
        if (!ObjectUtils.isEmpty(idMedstCgMap) && "0".equals(idMedstCgMap.get("PJZT").toString())) {
            resJson.put("code", "300");
            resJson.put("msg", "已开票");
            resJson.put("data", "票据地址:" + ClobToString((Clob) idMedstCgMap.get("pictureNetUrl")));
            log.info("已开票！DZPJHM:" + idMedstCgMap.get("BILLNO"));
            return resJson.toString();
        }

        Map<String, Object> orgMap = invoicingBoozMapper.getOrgInfo(idMedstCg);
        if (ObjectUtils.isEmpty(orgMap)) {
            resJson.put("code", "500");
            resJson.put("msg", "未获取到有效的机构ID[ID_ORG]或者已经取消结算!");
            resJson.put("data", parmMap.get("jgid"));
            log.info("已开票！DZPJHM:" + idMedstCgMap.get("BILLNO"));
            return resJson.toString();
        }
        String idOrg = orgMap.get("ID_ORG").toString();
        //根据机构ID:去分别获取不同机构对应的博思的  AppId 和 key
        ETicketsEnum eTicketsEnum = ETicketsEnum.getETicketsEnumByOrg(orgMap.get("ID_ORG").toString());

        //组装请求博思请求头
        String appid = eTicketsEnum.getAppid();
        String key = eTicketsEnum.getKey();
        String pjUrl = "";
        String reHitFlag = parmMap.get("reHitFlag").toString();//补打标志

        Map<String, Object> dzfpXtcsMap = invoicingBoozMapper.getXTCS(idOrg, "DZFP_URL");
        if (ObjectUtils.isEmpty(dzfpXtcsMap)) {
            resJson.put("code", "500");
            resJson.put("msg", "未找到[dzfpXtcsMap]机构[ID_ORG" + idOrg + "]对应的开票地址!");
            resJson.put("data", parmMap.get("jgid"));
            return resJson.toString();
        }
        pjUrl = dzfpXtcsMap.get("VAL_PARAM").toString();
        if (ObjectUtils.isEmpty(pjUrl)) {
            resJson.put("code", "500");
            resJson.put("msg", "未找到[pjUrl]机构[ID_ORG" + idOrg + "]对应的开票地址!");
            resJson.put("data", parmMap.get("jgid"));
            return resJson.toString();
        }

        String signElementKeyVersion = "&key=" + key + "&version=1.0";
        String signElementAppid = "appid=" + appid;

        SnowFlakeUtil snowFlakeUtil = new SnowFlakeUtil(2, 0);
        Long noise = snowFlakeUtil.nextId();

        Map<String, Object> body = new HashMap<>();
        body.put("appid", appid);
        body.put("noise", noise.toString());
        body.put("version", "1.0");


        //电子票据
        if ("114".equals(sdVistpCd)) {//114 住院
            resJson = E_Tickets_Hospital(resJson, body, idMedstCg, sdVistpCd, signElementKeyVersion, signElementAppid, idOrg, pjUrl, reHitFlag);
            return resJson.toString();
        } else if ("111".equals(sdVistpCd) || "112".equals(sdVistpCd) || "113".equals(sdVistpCd)) {//111 门诊 112挂号 113 体检
            resJson = E_Tickets_Outpatient(resJson, body, idMedstCg, sdVistpCd, signElementKeyVersion, signElementAppid, idOrg, pjUrl, reHitFlag);
            return resJson.toString();
        } else {
            resJson.put("code", "500");
            resJson.put("msg", "目前占不支持sdVistpCd除[111/114]以外的其他类型开票");
            resJson.put("data", new Object());
        }


        return resJson.toString();
    }


    public JSONObject E_Tickets_Hospital(JSONObject resJson, Map<String, Object> body, String idMedstCg, String sdVistpCd, String signElementKeyVersion,
                                         String signElementAppid, String idOrg, String pjUrl, String reHitFlag) {

        resJson.put("code", "200");
        resJson.put("msg", "住院电子票据上传成功!");
        resJson.put("data", new Object());

        Map<String, Object> hospitalMap = invoicingBoozMapper.getBsinvEBillHospitalized(idMedstCg, sdVistpCd);
        if (ObjectUtils.isEmpty(hospitalMap)) {
            resJson.put("code", "500");
            resJson.put("msg", "未找到有效的住院结算信息[HI_BIL_MED_PIRECI_IP.ID_MEDSTIP:" + idMedstCg + "]!");
            resJson.put("data", new Object());
            return resJson;
        }

        if ("1".equals(reHitFlag)) {//如果是补打程序
            hospitalMap.put("busNo", hospitalMap.get("busNo") + "BD");
        }

        List<Map<String, Object>> chargeDetail = new ArrayList<>();
        List<Map<String, Object>> listDetail = new ArrayList<>();
        List<Map<String, Object>> payChannelDetail = new ArrayList<>();
        List<Map<String, Object>> otherInfos = new ArrayList<>();

        List<Map<String, Object>> otherMedicalList = new ArrayList<>();
        List<Map<String, Object>> payMentVoucherList = new ArrayList<>();
        List<Map<String, Object>> payOrderInfoList = new ArrayList<>();

        chargeDetail = invoicingBoozMapper.chargeDetail_zy(idMedstCg);
        listDetail = invoicingBoozMapper.listDetail_zy(idMedstCg);
        payChannelDetail = invoicingBoozMapper.payChannelDetail_zy(idMedstCg);
        otherInfos = invoicingBoozMapper.otherInfos_zy(idMedstCg);

        hospitalMap.put("chargeDetail", chargeDetail);
        hospitalMap.put("listDetail", listDetail);
        hospitalMap.put("payChannelDetail", payChannelDetail);
        hospitalMap.put("otherInfo", otherInfos);

        hospitalMap.put("otherMedicalList", otherMedicalList);
        hospitalMap.put("payOrderInfoList", payOrderInfoList);
        hospitalMap.put("payMentVoucherList", payMentVoucherList);

        //组装参数
        String json = JSONUtil.toJson(hospitalMap);
        String encodeDatas = BASE64Encrypt(json);//加密
        String sign = signElementAppid + "&data=" + encodeDatas + "&noise=" + body.get("noise") + signElementKeyVersion;
        sign = DigestUtils.md5DigestAsHex(sign.getBytes()).toUpperCase();
        body.put("sign", sign);
        body.put("data", encodeDatas);

        try {
            String bodyParJson = JSONUtil.toJson(body);
            log.info("住院票据上传[明文]入参:" + (json.length() > 1000 ? json.substring(0, 1000) : json));
            log.info("住院票据上传[密文]入参:" + (bodyParJson.length() > 1000 ? bodyParJson.substring(0, 1000) : bodyParJson));
            String res = HttpClientUtil.doPostJsonPj(pjUrl + "invEBillHospitalized", bodyParJson, "1");
            log.info("住院票据上传出参:" + res);

            if (!ObjectUtils.isEmpty(res)) {
                Map<String, Object> resMap = JSONUtil.toHashMap(res);
                String data = (String) resMap.get("data");
                data = BASE64Decrypt(data);

                Map<String, Object> dataMap = JSONUtil.toHashMap(data);
                if ("S0000".equals(dataMap.get("result").toString())) {//如果成功了 就写电子票表
                    log.error("进入票据表回写:{}", idMedstCg);
                    String messageStr = dataMap.get("message").toString();
                    messageStr = BASE64Decrypt(messageStr);
                    Map<String, Object> messageMap = JSONUtil.toHashMap(messageStr);
                    //保存E_BILL_ISSUED
                    Map<String, Object> eBillMap = new HashMap<>();
                    eBillMap.put("JGID", idOrg);//机构id
                    eBillMap.put("YWLX", 3);//业务类型 1挂号 2门诊 3住院
                    eBillMap.put("GLXH", idMedstCg);//关联序号发票号码
                    eBillMap.put("DZPJDM", messageMap.get("billBatchCode"));//电子票据代码
                    eBillMap.put("DZPJHM", messageMap.get("billNo"));//电子票据号码
                    eBillMap.put("DZJYM", messageMap.get("random"));//电子校验码
                    eBillMap.put("DZPJYM", messageMap.get("pictureUrl"));//电子票据页面
                    eBillMap.put("PJZT", 0);//票据状态:0正常电子票 1纸质票 2已冲红
                    eBillMap.put("JSCS", 1);//结算次数 默认1
                    eBillMap.put("YWID", hospitalMap.get("hisFphm"));//业务id
                    eBillMap.put("YYFPHM", hospitalMap.get("hisFphm"));//发票号码
                    eBillMap.put("KPJE", hospitalMap.get("totalAmt"));//开票金额
                    eBillMap.put("DZPJTP", messageMap.get("billQRCode"));//电子票据图片
                    /*if (ObjectUtils.isEmpty(messageMap.get("wxCardUrl")) && !ObjectUtils.isEmpty(messageMap.get("pictureNetUrl"))){
                        String qrCodeBase64Str = QrCodeUtil.httpToQrCodeBase64(messageMap.get("pictureNetUrl").toString());
                        eBillMap.put("DZPJTP", qrCodeBase64Str);//电子票据图片
                    }*/
                    eBillMap.put("DZPJWWYM", ObjectUtils.isEmpty(messageMap.get("wxCardUrl")) ? messageMap.get("pictureNetUrl") + "" : messageMap.get("wxCardUrl"));//电子票据外网页面
                    //如果是补打的情况就先删除原来的发票在插入新开的发票 ,(当新开第一次发票后又冲红发票 .在进行第二次开即为补打)
                    invoicingBoozMapper.deleteDzfp(eBillMap);


                    invoicingBoozMapper.saveDzfp(eBillMap);
                    resJson.put("data", JSONUtil.map2Json(eBillMap));
                } else {//失败了 就写一个失败的日志表
                    String messageStr = dataMap.get("message").toString();
                    messageStr = BASE64Decrypt(messageStr);
                    log.error("开票失败:结算id[ID_MEDSTOE:" + idMedstCg + "],异常::" + messageStr);
                    resJson.put("code", "500");
                    resJson.put("msg", "电子票据上传返回失败:" + messageStr);
                    resJson.put("data", new Object());
                }
            } else {
                resJson.put("code", "500");
                resJson.put("msg", "电子票据上传无返回[超时]!");
                resJson.put("data", new Object());
            }
        } catch (Exception e) {
            resJson.put("code", "500");
            resJson.put("msg", "住院电子票据上传数据解析异常:" + e);
            resJson.put("data", new Object());
        }

        return resJson;
    }

    public JSONObject E_Tickets_Outpatient(JSONObject resJson, Map<String, Object> body, String idMedstCg, String sdVistpCd, String signElementKeyVersion,
                                           String signElementAppid, String idOrg, String pjUrl, String reHitFlag) {
        resJson.put("code", "200");
        resJson.put("msg", "门诊电子票据上传成功!");
        resJson.put("data", new Object());

        Map<String, Object> outpatientMap = invoicingBoozMapper.getBsInvoiceEBillOutpatient(idMedstCg, sdVistpCd);
        if (ObjectUtils.isEmpty(outpatientMap)) {
            resJson.put("code", "500");
            resJson.put("msg", "未找到有效的么门诊结算信息!");
            resJson.put("data", new Object());
            return resJson;
        }

        if ("1".equals(reHitFlag)) {//如果是补打程序
            outpatientMap.put("busNo", outpatientMap.get("busNo") + "BD");
        }


        List<Map<String, Object>> chargeDetail = new ArrayList<>();
        List<Map<String, Object>> listDetail = new ArrayList<>();
        List<Map<String, Object>> payChannelDetail = new ArrayList<>();
        List<Map<String, Object>> otherInfos = new ArrayList<>();

        List<Map<String, Object>> otherMedicalList = new ArrayList<>();
        List<Map<String, Object>> payMentVoucherList = new ArrayList<>();
        List<Map<String, Object>> payOrderInfoList = new ArrayList<>();

        chargeDetail = invoicingBoozMapper.chargeDetail(idMedstCg);
        listDetail = invoicingBoozMapper.listDetail(idMedstCg);
        payChannelDetail = invoicingBoozMapper.payChannelDetail(idMedstCg);
        otherInfos = invoicingBoozMapper.otherInfos(idMedstCg);


        outpatientMap.put("chargeDetail", chargeDetail);
        outpatientMap.put("listDetail", listDetail);
        outpatientMap.put("payChannelDetail", payChannelDetail);
        outpatientMap.put("otherInfo", otherInfos);

        outpatientMap.put("otherMedicalList", otherMedicalList);
        outpatientMap.put("payOrderInfoList", payOrderInfoList);
        outpatientMap.put("payMentVoucherList", payMentVoucherList);

        //组装参数
        String json = JSONUtil.toJson(outpatientMap);
        String encodeDatas = BASE64Encrypt(json);//加密
        String sign = signElementAppid + "&data=" + encodeDatas + "&noise=" + body.get("noise") + signElementKeyVersion;
        sign = DigestUtils.md5DigestAsHex(sign.getBytes()).toUpperCase();
        body.put("sign", sign);
        body.put("data", encodeDatas);

        try {
            String bodyParJson = JSONUtil.toJson(body);
            log.info("门诊票据上传明文入参" + json);
            log.info("门诊票据上传加密入参" + bodyParJson);
            String res = HttpClientUtil.doPostJsonPj(pjUrl + "invoiceEBillOutpatient", bodyParJson, "1");
            log.info("门诊票据上传出参" + res);

            if (!ObjectUtils.isEmpty(res)) {
                Map<String, Object> resMap = JSONUtil.toHashMap(res);
                String data = (String) resMap.get("data");
                data = BASE64Decrypt(data);

                Map<String, Object> dataMap = JSONUtil.toHashMap(data);
                if ("S0000".equals(dataMap.get("result").toString())) {//如果成功了 就写电子票表
                    log.error("进入票据表回写:{}", idMedstCg);
                    String messageStr = dataMap.get("message").toString();
                    messageStr = BASE64Decrypt(messageStr);
                    Map<String, Object> messageMap = JSONUtil.toHashMap(messageStr);
                    //保存E_BILL_ISSUED
                    Map<String, Object> eBillMap = new HashMap<>();
                    eBillMap.put("JGID", idOrg);//机构id
                    eBillMap.put("YWLX", "112".equals(sdVistpCd) ? 1 : 2);//业务类型 1挂号 2门诊 3住院
                    eBillMap.put("GLXH", idMedstCg);//关联序号 his结算id
                    eBillMap.put("DZPJDM", messageMap.get("billBatchCode"));//电子票据代码
                    eBillMap.put("DZPJHM", messageMap.get("billNo"));//电子票据号码
                    eBillMap.put("DZJYM", messageMap.get("random"));//电子校验码
                    eBillMap.put("DZPJYM", messageMap.get("pictureUrl"));//电子票据页面
                    eBillMap.put("PJZT", 0);//票据状态:0正常电子票 1纸质票 2已冲红
                    eBillMap.put("JSCS", 1);//结算次数 默认1
                    eBillMap.put("YWID", outpatientMap.get("hisFphm"));//业务id
                    eBillMap.put("YYFPHM", outpatientMap.get("hisFphm"));//发票号码
                    eBillMap.put("KPJE", outpatientMap.get("totalAmt"));//开票金额
                    eBillMap.put("DZPJTP", messageMap.get("billQRCode"));//电子票据图片
                    //博思版本为v3的时候放开
                    /*if (ObjectUtils.isEmpty(messageMap.get("wxCardUrl")) && !ObjectUtils.isEmpty(messageMap.get("pictureNetUrl"))){
                        String qrCodeBase64Str = QrCodeUtil.httpToQrCodeBase64(messageMap.get("pictureNetUrl").toString());
                        eBillMap.put("DZPJTP", qrCodeBase64Str);//电子票据图片
                    }*/
                    eBillMap.put("DZPJWWYM", ObjectUtils.isEmpty(messageMap.get("wxCardUrl")) ? messageMap.get("pictureNetUrl") + "" : messageMap.get("wxCardUrl"));//电子票据外网页面
                    invoicingBoozMapper.saveDzfp(eBillMap);
                    resJson.put("data", JSONUtil.map2Json(eBillMap));
                } else {//失败了 就写一个失败的日志表
                    String messageStr = dataMap.get("message").toString();
                    messageStr = BASE64Decrypt(messageStr);
                    log.error("开票失败:结算id[ID_MEDSTOE:" + idMedstCg + "],异常::" + messageStr);
                    resJson.put("code", "500");
                    resJson.put("msg", "电子票据上传返回失败:" + messageStr);
                    resJson.put("data", new Object());
                }
            } else {
                resJson.put("code", "500");
                resJson.put("msg", "电子票据上传无返回[超时]");
                resJson.put("data", new Object());

            }
        } catch (Exception e) {
            resJson.put("code", "500");
            resJson.put("msg", "门诊电子票据上传数据解析异常:" + e);
            resJson.put("data", new Object());
        }
        return resJson;
    }

    @MultiDataSourceTransactional(transactionManagers = {"hisTransactionManager"})
    @Override
    public String BS_E_TicketsFlushRed(Map<String, Object> parmMap) {

        JSONObject resJson = new JSONObject();

        isNUll(parmMap, Arrays.asList("invoiceNumber", "euDirect", "jgid", "medstoeid"));
        //根据发票号码查询结算的信息
        Map<String, Object> req = invoicingBoozMapper.getSettlementInfoCan(parmMap);
        if (ObjectUtils.isEmpty(req)) {
            resJson.put("code", "500");
            resJson.put("msg", "未找到HIS结算信息[FPHM:" + parmMap.get("invoiceNumber") + "]!");
            resJson.put("data", new Object());
            return resJson.toString();
        }

        req.put("euDirect", parmMap.get("euDirect").toString());
        isNUll(req, Arrays.asList("idMedstCg", "sdVistpCd", "euDirect"));
        if (!"-1".equals(req.get("euDirect").toString())) {
            resJson.put("code", "500");
            resJson.put("msg", "该业务是电子发票开票euDirect应该传-1");
            resJson.put("data", new Object());
            return resJson.toString();
        }

        String sdVistpCd_Name = "";
        String idMedstCg = req.get("idMedstCg").toString();
        String sdVistpCd = req.get("sdVistpCd").toString();//111 门诊 112 急诊 114 住院


        if ("111".equals(sdVistpCd)) {
            sdVistpCd_Name = "门诊" + sdVistpCd + "]";
        } else if ("112".equals(sdVistpCd)) {
            sdVistpCd_Name = "急诊" + sdVistpCd + "]";
        } else if ("114".equals(sdVistpCd)) {
            sdVistpCd_Name = "住院" + sdVistpCd + "]";
        } else {
            resJson.put("code", "500");
            resJson.put("msg", "不在业务范围内[" + sdVistpCd + "]");
            resJson.put("data", new Object());
            return resJson.toString();
        }

        // 根据结算id去查原开票信息
        Map<String, Object> bodyMap = invoicingBoozMapper.getBsInvoiceState(idMedstCg, parmMap.get("invoiceNumber").toString());
        if (!CollectionUtil.isEmpty(bodyMap) && "2".equals(bodyMap.get("PJZT").toString())) {
            log.info("未查询博思电子票据开票信息：" + idMedstCg);
            resJson.put("code", "500");
            resJson.put("msg", "票据已冲红:" + idMedstCg);
            resJson.put("data", ClobToString((Clob) bodyMap.get("pictureNetUrl")));
            bodyMap.put("pictureNetUrl", ClobToString((Clob) bodyMap.get("pictureNetUrl")));
            return resJson.toString();
        } else if (!CollectionUtil.isEmpty(bodyMap) && "0".equals(bodyMap.get("PJZT").toString())) {

        } else {
            resJson.put("code", "500");
            resJson.put("msg", "未找到有效的开票信息:" + idMedstCg);
            resJson.put("data", new Object());
            resJson.put("data", ClobToString((Clob) bodyMap.get("pictureNetUrl")));
            return resJson.toString();
        }

        bodyMap.put("pictureNetUrl", ClobToString((Clob) bodyMap.get("pictureNetUrl")));

        String idOrg = bodyMap.get("ID_ORG").toString();
        //根据机构ID:去分别获取不同机构对应的博思的  AppId 和 key
        ETicketsEnum eTicketsEnum = ETicketsEnum.getETicketsEnumByOrg(bodyMap.get("ID_ORG").toString());

        //组装请求博思请求头
        String appid = eTicketsEnum.getAppid();
        String key = eTicketsEnum.getKey();
        String pjUrl = "";

        Map<String, Object> dzfpXtcsMap = invoicingBoozMapper.getXTCS(idOrg, "DZFP_URL");
        if (ObjectUtils.isEmpty(dzfpXtcsMap)) {
            resJson.put("code", "500");
            resJson.put("msg", "未找到[dzfpXtcsMap]机构[ID_ORG" + idOrg + "]对应的开票地址!");
            resJson.put("data", parmMap.get("jgid"));
            return resJson.toString();
        }
        pjUrl = dzfpXtcsMap.get("VAL_PARAM").toString();
        if (ObjectUtils.isEmpty(pjUrl)) {
            resJson.put("code", "500");
            resJson.put("msg", "未找到[pjUrl]机构[ID_ORG" + idOrg + "]对应的开票地址!");
            resJson.put("data", parmMap.get("jgid"));
            return resJson.toString();
        }

        String signElementKeyVersion = "&key=" + key + "&version=1.0";
        String signElementAppid = "appid=" + appid;

        SnowFlakeUtil snowFlakeUtil = new SnowFlakeUtil(2, 0);
        Long noise = snowFlakeUtil.nextId();

        Map<String, Object> body = new HashMap<>();
        body.put("appid", appid);
        body.put("noise", noise.toString());
        body.put("version", "1.0");

        //组装参数
        String json = JSONUtil.toJson(bodyMap);
        String encodeDatas = BASE64Encrypt(json);//加密
        String sign = signElementAppid + "&data=" + encodeDatas + "&noise=" + body.get("noise") + signElementKeyVersion;
        sign = DigestUtils.md5DigestAsHex(sign.getBytes()).toUpperCase();
        body.put("sign", sign);
        body.put("data", encodeDatas);
        String bodyParJson = JSONUtil.toJson(body);
        try {
            log.info("票据冲正[明文]入参::" + json);
            log.info("票据冲正[加密]入参::" + bodyParJson);
            String res = HttpClientUtil.doPostJsonPj(pjUrl + "writeOffEBill", bodyParJson, "1");
            log.info("票据冲正[加密]出参::" + res);
            if (!ObjectUtils.isEmpty(res)) {
                Map<String, Object> resMap = JSONUtil.toHashMap(res);

                String data = (String) resMap.get("data");
                data = BASE64Decrypt(data);

                Map<String, Object> dataMap = JSONUtil.toHashMap(data);
                if ("S0000".equals(dataMap.get("result").toString())) {//如果成功了 就写电子票表
                    log.error("进入票据表回写:{}", idMedstCg);
                    String messageStr = dataMap.get("message").toString();
                    messageStr = BASE64Decrypt(messageStr);
                    Map<String, Object> messageMap = JSONUtil.toHashMap(messageStr);
                    //保存E_BILL_ISSUED
                    Map<String, Object> eBillMap = new HashMap<>();
                    eBillMap.put("JGID", idOrg);//机构id
                    eBillMap.put("GLXH", idMedstCg);//关联序号发票号码
                    eBillMap.put("YYFPHM", parmMap.get("invoiceNumber"));//关联序号发票号码
//                    eBillMap.put("YWID", idMedstCg);//业务id

                    eBillMap.put("PJZT", 2);//票据状态:0正常电子票 1纸质票 2已冲红

                    eBillMap.put("HPDM", messageMap.get("eScarletBillBatchCode"));//红票代码
                    eBillMap.put("HPHM", messageMap.get("eScarletBillNo"));//红票号码
                    eBillMap.put("HPJYM", messageMap.get("eScarletRandom"));//红票校验码
                    eBillMap.put("HPYM", messageMap.get("pictureUrl"));//红票页面
                    eBillMap.put("HPWWYM", messageMap.get("pictureNetUrl") + "");//红票外网页面
                    eBillMap.put("DZPJTP", messageMap.get("billQRCode"));//红票外网页面
                    eBillMap.put("YWID", "114".equals(sdVistpCd) ? parmMap.get("invoiceNumber") + "R" : parmMap.get("invoiceNumber"));//红票外网页面

                    int a = invoicingBoozMapper.updateDzfp(eBillMap);
                    if (a == 0) {
                        log.error("票据冲正更新gy_dzfp失败!");
                        resJson.put("code", "300");
                        resJson.put("msg", "票据冲正成功,更新GY_DZFP失败!");
                        resJson.put("data", new Object());
                    } else {
                        resJson.put("data", JSONUtil.map2Json(eBillMap));
                        resJson.put("code", "200");
                        resJson.put("msg", "票据冲红成功!");
                    }

                } else {//失败了 就写一个失败的日志表
                    String messageStr = dataMap.get("message").toString();
                    messageStr = BASE64Decrypt(messageStr);
                    log.error("票据冲正失败:结算id[ID_MEDSTOE:" + idMedstCg + "]::" + messageStr);
                    resJson.put("code", "500");
                    resJson.put("msg", "票据冲正返回失败:" + messageStr);
                    resJson.put("data", new Object());
                }
            } else {
                resJson.put("code", "500");
                resJson.put("msg", "票据冲正超时!");
                resJson.put("data", new Object());
            }
        } catch (Exception e) {
            resJson.put("code", "500");
            resJson.put("msg", "电子发票冲红数据解析异常:" + e);
            resJson.put("data", new Object());

        }
        return resJson.toString();
    }

    //空
    @Override
    public String queryInvoiceById(Map<String, Object> req) {
        isNUll(req, "invoiceNumber");

        JSONObject resJson = new JSONObject();

        String jgid = req.get("ID_ORG").toString();
        String invoiceNumber = req.get("invoiceNumber").toString();
        List<Map<String, Object>> empty = new ArrayList<>();
        List<Map<String, Object>> invoiceList = invoicingBoozMapper.getInvoiceList(jgid, invoiceNumber);

        resJson.put("code", "200");
        resJson.put("msg", "发票信息查询成功!");
        resJson.put("data", CollectionUtils.isEmpty(invoiceList) ? empty : JSONArray.fromObject(invoiceList));
        return resJson.toString();
    }


    /**
     * 获取机构信息
     */
    @Override
    public String queryHospitalList(Map<String, Object> req) {

        JSONObject resJson = new JSONObject();
        List<Map<String, Object>> empty = new ArrayList<>();
        //fg_active 激活标志
        List<Map<String, Object>> hospitalList = invoicingBoozMapper.queryHospitalList(hospitalID);

        resJson.put("code", "200");
        resJson.put("msg", "获取机构信息!");
        resJson.put("data", CollectionUtils.isEmpty(hospitalList) ? empty : JSONArray.fromObject(hospitalList));
        return resJson.toString();
    }


    //HIHIS电子票据定时上传.只上传没有开票的 补传
    @Override
    public void invoiceEBillAll() {
        List<Map<String, Object>> hospitalList = invoicingBoozMapper.queryFillInvoiceEBill();
        try {
            hospitalList.forEach(map -> {
                String s = BS_E_TicketsInvoicing(map);
                log.info("定时任务补传电子发票::{}:{}:返回结果::{}", map.get("medstoeid"), map.get("invoiceNumber"), s);
            });
        } catch (Exception e) {
            e.printStackTrace();
            log.info("电子票据上传异常:{}", e);
        }
    }

    //空
    @Override
    public String queryVisInfoById(Map<String, Object> req) throws Exception {
        //isNUll(req,Arrays.asList("numberVis","idOrg"));

        JSONObject resJson = new JSONObject();
        List<Map<String, Object>> empty = new ArrayList<>();

        List<Map<String, Object>> invoiceList = invoicingBoozMapper.queryVisInfoById(req);
        invoiceList.stream().forEach(invoiceMap -> {
            Object clobObj = invoiceMap.get("DZPJWWYM");
            if (clobObj instanceof Clob) {
                String clobStr = ClobToString((Clob) clobObj);
                invoiceMap.put("DZPJWWYM", clobStr);
            }
        });

        resJson.put("code", "200");
        resJson.put("msg", "发票信息查询成功!");
        resJson.put("data", CollectionUtils.isEmpty(invoiceList) ? empty : JSONArray.fromObject(invoiceList));
        return resJson.toString();
    }

    /*将电子发票信息上传医保局: 医保交易4901*/
    @Override
    public String uplodMedica(Map<String, Object> req) throws Exception {
        isNUll(req, Arrays.asList("invoiceList", "idOrg"));

        //获取机构对应的医保参数
        Map<String,Object> gjybApiMap = invoicingBoozMapper.getGJYBAPI(req);
        if (gjybApiMap.isEmpty()) throw new CommonException("获取["+req.get("idOrg")+"]国家医保参数失败!");

        JSONObject resJson = new JSONObject();
        List<Map<String, Object>> invoiceList = (List<Map<String, Object>>) req.get("invoiceList");
        List<String> urlList = new ArrayList<>();
        List<String> pdfNameList = new ArrayList<>();

        //pdf版式文件命名 电子结算凭证代码-电子结算凭证号码
        invoiceList.forEach(
                invoiceMap -> {
                    urlList.add(invoiceMap.get("DZPJWWYM").toString());
                    pdfNameList.add(invoiceMap.get("DZPJDM") + "-" + invoiceMap.get("DZPJHM") + ".pdf");
                }
        );
        String base64Zip = urlListToBase64Zip(urlList, pdfNameList);
        String bizStsb = processDates(invoiceList);
        //获取oracle的SEQUENCE
        /*CREATE SEQUENCE seq_ydyb_xsbx_pch
  START WITH 1        -- 起始值（对应业务上的00001）
  INCREMENT BY 1      -- 递增间隔1
  MINVALUE 1          -- 最小值1（避免负数）
  MAXVALUE 99999      -- 可选：最大值（如5位则设为99999）
  NOCYCLE             -- 不循环（达到最大值后不再递增）
  CACHE 20;           -- 缓存20个值（提升性能，非必须）*/
        //String  seqNum = invoicingBoozMapper.getSeq("seq_ydyb_xsbx_pch");
        String seqNum = "00001";
        //上传批次号
        String upldBchno = DateUtil.format(new Date(), "yyyyMMdd") + seqNum;
        String fixmedinsCode = invoiceList.get(0).get("FIXMEDINSCODE").toString();
        String fixmedinsName = invoiceList.get(0).get("FIXMEDINSNAME").toString();
        String insuplcAdmdvs = invoiceList.get(0).get("INSUPLC_ADMDVS").toString();
        int elecSetlCertCnt = invoiceList.size();

        //组装:结算凭证关联关系列表->数量与版式文件压缩包内版式文件数量相同
        List<Map<String, Object>> upldDetlList = new ArrayList<>();
        for (Map<String, Object> invoiceMap : invoiceList) {
            Map<String, Object> upldDetlMap = new LinkedHashMap<>();
            upldDetlMap.put("elecSetlCertCode", invoiceMap.get("DZPJDM"));//非必填   电子结算凭证代码 1 财政电子票据、2 税务电子发票必填； 3 税务数电票非必填
            upldDetlMap.put("elecSetlCertNo", invoiceMap.get("DZPJHM"));//必填 电子结算凭证号码
            upldDetlMap.put("elecSetlCertChkcode", Integer.parseInt(invoiceMap.get("DZJYM").toString()));//非必填 电子结算凭证校验码 1 财政电子票据、2 税务电子发票必填 3 税务数电票非必填
            upldDetlMap.put("elecSetlCertType", "1");//必填 电子结算凭证类型 1 财政电子票据、2 税务电子发票必填； 3 税务数电票非必填
            upldDetlMap.put("certSetlType", "2");//必填 凭证结算类型 1 实时结算 2 全自费
            upldDetlMap.put("mdtrtType", StringUtils.equals("3", invoiceMap.get("YWLX").toString()) ? "2" : "1");//必填 就诊类型 1门诊 2住院
            upldDetlMap.put("billDate", DateUtil.parse(invoiceMap.get("SCRQ").toString().substring(0,10),"yyyy-MM-dd"));//必填 开票日期 yyyy-MM-dd
            upldDetlMap.put("medfeeSumamt", NumberUtil.toBigDecimal(invoiceMap.get("AMT_PIRECIS").toString()));// 必填 医疗费总额
            upldDetlMap.put("insuplcAdmdvs", invoiceMap.get("INSUPLC_ADMDVS").toString());//必填 参保地医保区划 参保地医保区划,实时结算必填
            upldDetlMap.put("bizSn", invoiceMap.get("NUM_VISMED").toString());//必填 业务流水号 实时结算填药机构结算ID,medinsSetlId,全自费填医疗机构就诊ID fixmedinsMdtrtId
            upldDetlMap.put("setlId", null);//非必填 实时结算时必填
            upldDetlMap.put("setlTime", null);//非必填 结算时间 实时结算时必填 yyyy-MM-dd HH:mm:ss
            upldDetlMap.put("psnNo", invoiceMap.get("PSN_NO").toString());//非必填 人员编号 实时结算时必填
            upldDetlMap.put("psnName", invoiceMap.get("NA_PI").toString());//必填 人员姓名
            upldDetlMap.put("certno", invoiceMap.get("IDCARD").toString());//非必填 证件号码 身份证号码
            upldDetlMap.put("insutype", null);//非必填 险种类型 实时结算时必填
            upldDetlMap.put("inscpAmt", null);//非必填 符合范围金额 实时结算时必填
            upldDetlMap.put("hifpPay", null);//非必填 统筹基金支出 实时结算时必填
            upldDetlMap.put("maindiagCode", invoiceMap.get("CD_DIE_MAJ").toString());//必填 主要诊断代码
            upldDetlMap.put("maindiagName", invoiceMap.get("NA_DIE_MAJ").toString());//必填 主要诊断名称
            upldDetlMap.put("scddiagCode", null);//非必填 次要诊断代码
            upldDetlMap.put("scddiagName", null);//非必填 次要诊断名称
            upldDetlMap.put("mdtrtareaAdmdvs", invoiceMap.get("MDTRTAREA_ADMVS").toString());//必填 就医地医保区划
            upldDetlMap.put("extFld", null);//非必填 扩展字段 extFld 为 json 字符串,序列化成字 符串后长度不能超过 4000
            upldDetlList.add(upldDetlMap);
        }

        //组装4901参数
        Map<String, Object> medicaParm = new HashMap<>();
        medicaParm.put("fixmedinsCode", fixmedinsCode);//定点医疗机构编号 必填
        medicaParm.put("fixmedinsName", fixmedinsName);//定点医疗机构名称 必填
        medicaParm.put("elecSetlCertCnt", elecSetlCertCnt);//电子结算凭证数量 必填 最大100张
        medicaParm.put("bizStsb", bizStsb);//业务区间 必填 电子结算凭证开具 时间区间如 20220520-20220525
        medicaParm.put("upldBchno", upldBchno);//上传批次号 必填 上传日期年月日加 5 位顺序号如 2022052500001
        medicaParm.put("filename", req.get("FIXMEDINSCODE")+"-电子发票版式压缩包-"+upldBchno+".zip");//压缩包文件名称 必填
        medicaParm.put("ftfileCompac", base64Zip);//zip 压缩包，包含多张电子结算凭证版 式文件(pdf/ofd),版式文件命名规则为：电子结算凭证代码-电子结算凭证号码 必填
        medicaParm.put("upldDetlList", upldDetlList);//数量与版式文件压缩包内版式文件数量相同 必填
        medicaParm.put("upldMode", "1");//1 结构化数据xml文件从版式文件解析 2 结构化数据xml文件分离上传（不传默认为 1） 非必填

        NumIdutil numIdutil = NumIdutil.getMyIdUtil();
        String msgid = fixmedinsCode+DateUtil.format(new Date(),"yyyyMMddHHmmss")+numIdutil.getOrderNum();

        String reqStr = JSON.toJSONString(medicaParm);
        //构造请求调用医保参数
        Map<String, Object> commPramMap = new HashMap<>();
        commPramMap.put("fixmedinsCode",fixmedinsCode);//加密方式 不加密传空，加密传 SM4
        commPramMap.put("fixmedinsName",fixmedinsName);//定点医药机构名称
        commPramMap.put("inf_time",DateUtil.format(new Date(),INPUT_FORMATTER));//交易时间
        commPramMap.put("infno","4901");//交易编号
        commPramMap.put("data",reqStr);
        commPramMap.put("insuplcAdmdvs",insuplcAdmdvs);
        commPramMap.put("msgid",msgid);//发送方报文 ID 定点医药机构编号(12)+时间(14)+顺序号(4) 时间格式：yyyyMMddHHmmss
        commPramMap.put("mdtrtareaAdmvs","510100");
        commPramMap.put("opterCode","CY6828");//经办人
        commPramMap.put("opterName","管理员");//经办人姓名

        commPramMap.put("idOrg",req.get("idOrg"));//交易签到流水号

        //从his获取签到信息
        Map<String,Object> signMap = invoicingBoozMapper.getSign(commPramMap);
        if (signMap.isEmpty()) throw new CommonException("获取["+commPramMap.get("opterCode")+"]签到信息失败!");
        String signTime = signMap.get("SIGN_TIME").toString();
        boolean b = com.bsoft.utils.DateUtil.eqStrTime(signTime);
        if (!b) throw new CommonException("未获取到当天签到信息!");
        commPramMap.put("sign_no",signMap.get("SIGN_NO"));//交易签到流水号
        //此处需要注意是否需要去签到,如果需要还要实现签到流程
        String reqYBPramar = YbUtils.getReqYBPramar(commPramMap);
        //API_URL
        String res = HttpClientUtil.doPostJson(gjybApiMap.get("API_URL").toString(), reqYBPramar);

        resJson.put("code", "200");
        resJson.put("msg", "发票信息查询成功!");
        resJson.put("data", "");
        return resJson.toString();
    }


    public static void main(String[] args) throws Exception {
        String a = "{\"idOrg\":\"68400db9caaa060001b96f85\",\"invoiceList\":[{\"DZJYM\":\"8175dd\",\"DZPJWWYM\":\"https://www.chinaebill.cn/eips-wxapp-service/H5FinanceDisplay/showInvoice?invoice_code=51060125&invoice_number=0116258300&check_code=8175dd&app_id=Ke8XxUStK&security=33A71D047AA1B6AFDF0B9B979A68B360\",\"CD_PIRECI\":\"ghwy00000126\",\"ORGNAME\":\"平昌县人民医院\",\"SCRQ\":\"2025-08-06 14:48:23\",\"NA_DIE_MAJ\":\"2型糖尿病\",\"NA_PI\":\"周秀珍\",\"CBLX\":\"本地\",\"INSUPLC_ADMDVS\":\"511923\",\"MDTRTAREA_ADMVS\":\"511923\",\"NUM_VISMED\":\"250806001188\",\"DZPJHM\":\"0116258300\",\"ID_PI\":\"715366811897700352\",\"DZPJDM\":\"51060125\",\"ID_ORG\":\"68400db9caaa060001b96f85\",\"AMT_PIRECIS\":3,\"CD_DIE_MAJ\":\"E11.900\",\"ID_VISMED\":\"6892faa2fc16840015dbacfd\"},{\"DZJYM\":\"02a479\",\"DZPJWWYM\":\"https://www.chinaebill.cn/eips-wxapp-service/H5FinanceDisplay/showInvoice?invoice_code=51060125&invoice_number=0116258368&check_code=02a479&app_id=Ke8XxUStK&security=FC7141538CAE1CDD2AA6DC0D794AF5CF\",\"CD_PIRECI\":\"mjszjzf000003526\",\"ORGNAME\":\"平昌县人民医院\",\"SCRQ\":\"2025-08-06 15:10:46\",\"NA_DIE_MAJ\":\"2型糖尿病\",\"NA_PI\":\"周秀珍\",\"CBLX\":\"本地\",\"INSUPLC_ADMDVS\":\"511923\",\"MDTRTAREA_ADMVS\":\"511923\",\"NUM_VISMED\":\"250806001188\",\"DZPJHM\":\"0116258368\",\"ID_PI\":\"715366811897700352\",\"DZPJDM\":\"51060125\",\"ID_ORG\":\"68400db9caaa060001b96f85\",\"AMT_PIRECIS\":458.78,\"CD_DIE_MAJ\":\"E11.900\",\"ID_VISMED\":\"6892faa2fc16840015dbacfd\"}]}";
        Map<String, Object> hashMap = JSONUtil.toHashMap(a);
        InvoicingBoozServiceImpl invoicingBoozObj = new InvoicingBoozServiceImpl();
        invoicingBoozObj.uplodMedica(hashMap);
    }


    /**
     * 处理发票列表中的SCRQ日期，返回最小-最大日期字符串
     *
     * @param invoices 包含SCRQ字段的Map列表
     * @return 格式化后的日期字符串（如"2025-08-06"或"2025-08-06-2025-08-07"）
     */
    public String processDates(List<Map<String, Object>> invoices) {
        // 校验入参，避免空指针
        if (invoices == null || invoices.isEmpty()) {
            return "";
        }

        // 1. 提取有效SCRQ并转换为LocalDate（过滤空值和格式错误的日期）
        List<LocalDate> validDates = invoices.stream()
                .map(invoice -> (String) invoice.get("SCRQ")) // 提取SCRQ字符串（注意类型转换）
                .filter(scrq -> scrq != null && !scrq.trim().isEmpty()) // 过滤空值/空白字符串
                .map(scrq -> {
                    try {
                        // 解析日期（忽略时间部分）
                        return LocalDate.parse(scrq, INPUT_FORMATTER);
                    } catch (DateTimeParseException e) {
                        // 记录格式错误日志，跳过无效日期
                        System.err.println("无效的SCRQ格式：" + scrq + "，错误：" + e.getMessage());
                        return null;
                    }
                })
                .filter(date -> date != null) // 过滤解析失败的null值
                .collect(Collectors.toList());
        // 2. 处理无有效日期的情况
        if (validDates.isEmpty()) {
            return "";
        }

        // 3. 一次流操作获取最小和最大日期（优化性能，避免两次流遍历）
        Optional<LocalDate> minOpt = validDates.stream().min(LocalDate::compareTo);
        Optional<LocalDate> maxOpt = validDates.stream().max(LocalDate::compareTo);

        // 由于validDates非空，这里必然有值，直接get()
        LocalDate minDate = minOpt.get();
        LocalDate maxDate = maxOpt.get();
        // 4. 按规则拼接结果
        return minDate.equals(maxDate) ? minDate.format(OUTPUT_FORMATTER) : minDate.format(OUTPUT_FORMATTER) + "-" + maxDate.format(OUTPUT_FORMATTER);
    }


    /**
     * BASE64加密
     */
    public String BASE64Encrypt(String json) {
        BASE64Encoder base64Encoder = new BASE64Encoder();
        String encodeDatas = base64Encoder.encode(json.getBytes());//加密
        return encodeDatas;
    }

    /**
     * BASE64解密
     */
    public String BASE64Decrypt(String json) throws IOException {
        BASE64Decoder base64Decoder = new BASE64Decoder();
        byte[] data = base64Decoder.decodeBuffer(json.toString());//解密
        String byteToText = new String(data, "UTF-8");
        return byteToText;
    }
}
